namespace Magecrawl.Utilities {
    using System;
    using System.Xml;
    using System.Xml.Schema;

    public class DiceRoll {
        private static readonly Random random = new Random();

        public static readonly DiceRoll Invalid = new DiceRoll( rolls: -1, diceFaces: -1, toAdd: -1, multiplier: -1 );

        public static readonly DiceRoll Zero = new DiceRoll( rolls: 0, diceFaces: 0, toAdd: 0, multiplier: 1 );

        public short DiceFaces;

        public Decimal Multiplier;

        public short Rolls;

        public short ToAdd;

        public DiceRoll( int rolls, int diceFaces, int toAdd = 0, Decimal multiplier = 1 ) {
            this.Rolls = ( short ) rolls;
            this.DiceFaces = ( short ) diceFaces;
            this.ToAdd = ( short ) toAdd;
            this.Multiplier = multiplier;
        }

        public DiceRoll( string s ) {
            this.Rolls = 0;
            this.DiceFaces = 0;
            this.Multiplier = 1;
            this.ToAdd = 0;

            if ( s == "0" ) {
                return;
            }

            var damageParts = s.Split( new[] { ',' }, StringSplitOptions.RemoveEmptyEntries );

            if ( damageParts.Length > 0 ) {
                short.TryParse( damageParts[ 0 ], out this.Rolls );
            }

            if ( damageParts.Length > 1 ) {
                short.TryParse( damageParts[ 1 ], out this.DiceFaces );
            }

            if ( damageParts.Length > 2 ) {
                short.TryParse( damageParts[ 2 ], out this.ToAdd );
            }

            if ( damageParts.Length > 3 ) {
                decimal.TryParse( damageParts[ 3 ], out this.Multiplier );
            }
        }

        public void Add( DiceRoll other ) {
            // If the other is zero, nothing to do.
            if ( other.Equals( Zero ) ) {
                return;
            }

            // If we're zero, take their values
            if ( this.Equals( Zero ) ) {
                this.Rolls = other.Rolls;
                this.DiceFaces = other.DiceFaces;
                this.Multiplier = other.Multiplier;
                this.ToAdd = other.ToAdd;
                return;
            }

            // If neither are zero, the d'ness better match
            if ( this.DiceFaces != other.DiceFaces ) {
                throw new InvalidOperationException( string.Format( "Can't add dice rolls: {0} + {1}", this, other ) );
            }

            this.Rolls += other.Rolls;
            this.ToAdd += other.ToAdd;
            this.Multiplier += 1 - other.Multiplier;
        }

        public bool Equals( DiceRoll other ) { return this.Rolls == other.Rolls && this.DiceFaces == other.DiceFaces && this.ToAdd == other.ToAdd && this.Multiplier == other.Multiplier; }

        public short Roll() {
            short total = 0;
            for ( short i = 0; i < this.Rolls; i++ ) {
                total += ( short ) ( random.Next( this.DiceFaces ) + 1 );
            }
            return ( short ) ( this.Multiplier * ( total + this.ToAdd ) );
        }

        public int RollMaxDamage() { return ( int ) Math.Round( ( double ) ( this.Multiplier * ( this.DiceFaces * this.Rolls ) ) + this.ToAdd ); }

        public override string ToString() {
            var hasMult = this.Multiplier != 1;
            var hasConstant = this.ToAdd != 0;

            var multiplierFrontString = hasMult ? string.Format( "{0}*", this.Multiplier ) : "";
            var frontParen = hasMult || hasConstant ? "(" : "";
            var endParen = hasMult || hasConstant ? ")" : "";
            var constantSign = this.ToAdd >= 0 ? "+" : ""; // Minus will come with number
            var constantEnd = hasConstant ? string.Format( "{0}{1}", constantSign, this.ToAdd ) : "";

            // 1d1 doesn't look nice
            if ( !hasMult && !hasConstant && this.Rolls == 1 && this.DiceFaces == 1 ) {
                return "1";
            }

            return string.Format( "{0}{1}{2}d{3}{4}{5}", multiplierFrontString, frontParen, this.Rolls, this.DiceFaces, constantEnd, endParen );
        }

        #region SaveLoad
        public XmlSchema GetSchema() { return null; }

        public void ReadXml( XmlReader reader ) {
            reader.ReadStartElement();

            reader.ReadStartElement();
            this.Rolls = ( short ) reader.ReadContentAsInt();
            reader.ReadEndElement();

            reader.ReadStartElement();
            this.DiceFaces = ( short ) reader.ReadContentAsInt();
            reader.ReadEndElement();

            reader.ReadStartElement();
            this.Multiplier = ( short ) reader.ReadContentAsDouble();
            reader.ReadEndElement();

            reader.ReadStartElement();
            this.ToAdd = ( short ) reader.ReadContentAsInt();
            reader.ReadEndElement();

            reader.ReadEndElement();
        }

        public void WriteXml( XmlWriter writer ) {
            writer.WriteStartElement( "DamageRoll" );
            writer.WriteElementString( "Rolls", this.Rolls.ToString() );
            writer.WriteElementString( "NumberFaces", this.DiceFaces.ToString() );
            writer.WriteElementString( "Multiplier", this.Multiplier.ToString() );
            writer.WriteElementString( "AddSub", this.ToAdd.ToString() );
            writer.WriteEndElement();
        }
        #endregion
    }
}
